#include "calendar_period.hpp"

// the following code stems from http://wwwcdf.pd.infn.it/MLO/Calendars/Notes.html


bool calendar_period::isLeap(int year) 
{
  return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
}


const std::string calendar_period::week_day_as_string(int day, int month, int year)
{
	const std::string days_as_string_en[] = {
		"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
	};

	int idx = weekDay(day, month, year);
	
#ifdef MONDAY_IS_0
	idx++;
	idx = idx % 7;
#endif
	
	return days_as_string_en[idx];		

}



int calendar_period::weekDay(int day, int month, int year)
{
  // Returns the day of the week for the given date, assumed valid and
  // in the Gregorian calendar.  The returned value is: 0 for Sunday,
  // 1 for Monday, ..., 6 for Saturday; or, if the preprocessor symbol
  // MONDAY_IS_0 is defined: 0 for Monday, 1 for Tuesday, ..., 6 for
  // Sunday.

  int result;

  if (month < 3) {
    month += 12;
    --year;
  }

  result = day + (13 * month - 27)/5 + year + year/4
           - year/100 + year/400;

#ifdef MONDAY_IS_0
  result += 6;
#endif // MONDAY_IS_0

  return (result % 7);
}


#include <stdexcept>

// Gregorian Calendar started on October 15, 1582
const long calendar_period::gregorianStart = (31L * (10 + 12L * 1582) + 15);

long calendar_period::greg2jul(int day,  int month,  int year) 
{
  // From "Numerical Recipes in C", adapted

  if (year == 0) throw std::domain_error("There is no year 0");

  // There is no calendar year 0.  As a consequence, e.g., the year 10
  // BC of the calendar is the year -9 of the Julian system.  The
  // formal parameter "year" of this procedure is the *calendar* year,
  // positive (AD) or negative (BC).

  long jy = (year < 0 ? year + 1 : year);
  long jm;

  // Months before March are considered the last months of the
  // previous year

  if (month > 2) jm = month + 1;
  else {
    --jy;
    jm = month + 13;
  }

  long result = day + 1720995L;
  result += static_cast<long>(365.25 * jy);
  result += static_cast<long>(30.6001 * jm);

  if ((31L * (month + 12L * year) + day) >= gregorianStart) {
    long cent = static_cast<long>(0.01 * jy);
    result += 2 - cent;
    result += static_cast<long>(0.25 * cent);
  }

  return result;
}


#include <iostream>
#include <sstream>
//#include <stdexcept>

// Gregorian Calendar started on Julian Day 2299161
const long calendar_period::gregorianEpoch = 2299161L;

const int calendar_period::nMonths = 12;

/*
static int monthDays[nMonths + 1] = {
  0,
  31, 28, 31, 30, 31, 30,
  31, 31, 30, 31, 30, 31
};
*/

int calendar_period::monthDays[] = {
  0,
  31, 28, 31, 30, 31, 30,
  31, 31, 30, 31, 30, 31
};

void calendar_period::jul2greg(long  jul,  int & day,  int & month,  int & year) 
{
  // From "Software, Practice and Experience", Vol. 23 (1993),
  // pag. 384

  if (jul < gregorianEpoch) {
    std::ostringstream s;
    s << "Julian Day " << jul << " comes before Gregorian Calendar";
    throw std::domain_error(s.str());
  }

  // JD 1721425 corresponds to a (virtual) January 1, year 1 of the
  // Gregorian Calendar.  n400 is the number of completed 400 year
  // cycles; n100 the number of completed 100 year cycles not included
  // in n400; n4 the number of completed 4 year cycles not included in
  // n400 and n100; n1 the number of years not included in n400, n100
  // or n4.

  jul       -= 1721426;
  long n400  = jul / 146097;
  jul       %= 146097;
  long n100  = jul / 36524;
  jul       %= 36524;
  long n4    = jul / 1461;
  jul       %= 1461;
  long n1    = jul / 365;
  jul       %= 365;

  year = 400 * n400 + 100 * n100 + 4 * n4 + n1;

  if (n100 == 4   ||   n1 == 4) {
    // Day 366 of year "year"

    day   = 31;
    month = 12;

  } else {
    // Day "jul+1" of year "year+1"

    if (isLeap(++year)) monthDays[2] = 29;
    else                monthDays[2] = 28;

    ++jul;

    for (int i = 1;   i <= nMonths;   ++i) {
      if (jul <= monthDays[i]) {
        day   = jul;
        month = i;
        return;
      }
      jul -= monthDays[i];
    }
    std::cerr << "Can't happen\n";
  }
}


//#include <iostream>

long calendar_period::week(int day,  int month,  int year)
{
  // Returns the number of the ISO week in the year (from 1 to 53)
  // containing the given date (assumed valid, and in the Gregorian
  // calendar); more exactly, the returned value is yyyyww, where yyyy
  // is the year and ww the ISO week number.

  int m, start, yearDay, lastDay, lastSunday;

  //const int nMonths = 12;

 /* static int monthDays[nMonths + 1] = {
    0,
    31,  0, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
  };
*/

  // - Initializes correctly the number of days in February;
  // - stores in lastDay the total number of days in the given year;
  // - stores in yearDay the total number of days in the year not
  //   after the given date ("day-of-the-year").

  if (isLeap(year)) {
    monthDays[2] = 29;
    lastDay      = 366;
  } else {
    monthDays[2] = 28;
    lastDay      = 365;
  }

  for (m = 0,  yearDay = day;   ++m < month;   ) {
    yearDay += monthDays[m];
  }

  // - Computes in "start" the first day of the first week of this
  //   year ("start" may be negative if the beginning of the first
  //   week is in the previous year);
  // - computes in "lastSunday" the date of the last Sunday in
  //   December.
  //
  // weekDay(day, month, year) is a procedure that computes the day of
  // the week corresponding to a given date, returning 0 for Monday, 1
  // for Tuesday, ..., 6 for Sunday.

  start      = 4 - weekDay(4, 1, year);
  lastSunday = weekDay(31, 12, year);
  lastSunday = lastSunday == 6 ? 31 : 30-lastSunday;

  std::cout << "- The first ISO week of the year " << year;
#ifdef MONDAY_IS_0
        std::cout << " begins on Monday ";
#else
	std::cout << " begins on Sunday ";
#endif


  if (start < 1) {
    std::cout << 31+start << "-Dec-" << year-1 << std::endl;
  } else {
    std::cout << start << "-Jan-" << year << std::endl;
  }

  // After the following printout, "lastSunday" is changed to the
  // day-of-the-year number of the Sunday ending the last ISO week of
  // the current year.

  std::cout << "- The last ISO week of the year " << year;
#ifdef MONDAY_IS_0
            std::cout << " ends on Sunday ";
#else
	std::cout << " ends on Saturday ";
#endif

  if (31-lastSunday < 4) {
    std::cout << lastSunday << "-Dec-" << year << std::endl;
    lastSunday = lastDay - (31 - lastSunday);
  } else {
    std::cout << lastSunday+7-31 << "-Jan-" << year+1 << std::endl;
    lastSunday = lastDay + lastSunday+7-31;
  }

  // - If the day-of-the-year is before "start", we are in the last
  //   week of the previous year (the week of December 31);
  // - if not, and if the given date is after "lastSunday", we are in
  //   the first week of the next year;
  // - in all the other cases, we compute the number of the week
  //   normally.

  if (yearDay < start)      return week(31, 12, year-1);
  if (yearDay > lastSunday) return (year + 1)*100 + 1;

  return year*100 + ((yearDay - start) / 7 + 1);
}




//#include <stdexcept>

const long   calendar_period::frenchEpoch       = 2375840;
const double calendar_period::daysPerFrenchYear = 1460969.0 / 4000.0;

// The names of the 5 (or 6) additional days at the end of the normal
// calendar (that has 12 months of 30 days each, or 360 days)

static const char * sansCulNames [] = {
  "Invalide",
  "Jour de la Vertu",  "Jour du Genie",        "Jour du Travail",
  "Jour de la Raison", "Jour des Recompenses", "Jour de la Revolution"
};

// The names of the 12 months (coined by Fabre d'Eglantine)

static const char * frenchMonthsNames [] = {
  "Invalide",
  "Vendemiaire", "Brumaire", "Frimaire",  "Nivose",
  "Pluviose",    "Ventose",  "Germinal",  "Floreal",
  "Prairial",    "Messidor", "Thermidor", "Fructidor"
};



long calendar_period::french2jul(int fd,  int fm,  int fy) 
{
  // From Edward M. Reingold and Nachum Dershowitz - "Calendrical
  // Calculations: The Millennium Edition" (Cambridge University
  // Press, 2001) - page 238
  //
  // Essentially, all the days before the given date are summed to the
  // Julian Day number corresponding to the first year of the French
  // Revolutionary Calendar.

  if (fy < 1) {
    throw std::domain_error("Before the French Revolution");
  }

  long result = frenchEpoch - 1;

  result += 365 * --fy;
  result += fy / 4;
  result -= fy / 100;
  result += fy / 400;
  result -= fy / 4000;
  result += 30 * --fm;
  result += fd;

  return result;
}

void calendar_period::jul2french(long jd,  int & fd,  int & fm,  int & fy) 
{
  // From Edward M. Reingold and Nachum Dershowitz - "Calendrical
  // Calculations: The Millennium Edition" (Cambridge University
  // Press, 2001) - page 238
  //
  // A first approximation of the year in the French Revolutionary
  // Calendar is found, and refined; then, the precise date is
  // computed from the Julian Day number corresponding to the start of
  // the current Revolutionary year.

  if (jd < frenchEpoch) {
    throw std::domain_error("Before the French Revolution");
  }

  long approx = jd - frenchEpoch + 2;

  fy = static_cast<int>(static_cast<double>(approx)/daysPerFrenchYear) + 1;
  long startOfYear = french2jul(1, 1, fy);

  if  (jd < startOfYear) {
    startOfYear = french2jul(1, 1, --fy); // We were wrong by 1 year...
  }
  fm = (jd -= startOfYear) / 30;
  fd = jd - 30 * fm++ + 1;
}


/*
#define TEST_PROGRAM 1

#ifdef TEST_PROGRAM
  #include <iostream>
  #include <cstdlib>
#endif // TEST_PROGRAM

#include <string>

static const char rc[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' };
static const int rMin(1), rMax(3999);

std::string roman(
  int dec
) {
  static const size_t bufSize = 24;

  // Returns a std::string containing the representation, in Roman
  // numerals, of the decimal number "dec".  "rMin" and "rMax" define
  // the range we can represent with the numerals defined in "rc".

  if (dec < rMin   ||   dec > rMax) return "Out of range";

  char         buf[bufSize];
  const char * pc               = rc;
  char *       last             = buf + bufSize;

  *--last = '\0';

  do {
    if (int digit = dec % 10) {

      int ni = digit % 5;
      if (ni == 4) {
        if (digit == 4) *--last = *(pc+1);
        else            *--last = *(pc+2);
        *--last = *pc;

      } else {
        switch (ni) {
          case 0:
            *--last = *(pc+1);
            break;
          case 1:  case 2:  case 3:
            while (ni--) *--last = *pc;
            if (digit > 4) *--last = *(pc+1);
            break;
        }
      }
    }

    dec /= 10;
    pc  += 2;
  } while (dec);

  return last;
}

#ifdef TEST_PROGRAM
int main(
  int    argc,
  char * argv[]
) {
  if (argc == 1) {
    std::cerr << "Usage:\t" << argv[0] << " i1 [ ,i2 [ ,i3 [ "
              << "... ]]]\n\n\tConverts the command line "
              << "arguments (numbers in the range " << rMin
              << "..." << rMax << ")\n\tto Roman numerals.\n";
  } else {
    while (--argc) {
      int i = std::atoi(*++argv);
      std::cout << i << "\t==> " << roman(i) << std::endl;
    }
  }
}
#endif // TEST_PROGRAM

*/
